﻿'   Configuration.vb
'   WireBear Awesemulator
'
'   Created by Chris Kent
'   Copyright 2011 WireBear. All rights reserved
'
#Region "License Information"
'   WireBear Awesemulator is free software: you can redistribute it and/or modify
'   it under the terms of the GNU General Public License as published by
'   the Free Software Foundation, either version 3 of the License, or
'   (at your option) any later version.
'
'   WireBear Awesemulator is distributed in the hope that it will be useful,
'   but WITHOUT ANY WARRANTY; without even the implied warranty of
'   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
'   GNU General Public License for more details.
'
'   You should have received a copy of the GNU General Public License
'   along with WireBear Awesemulator - License.txt
'   If not, see <http://www.gnu.org/licenses/>.
#End Region


Imports System.Xml.Schema
Imports System.Xml
Imports Microsoft.VisualBasic.FileIO
Imports System.Collections.ObjectModel

Public Class Configuration

    Private Sub New()

    End Sub

#Region "Validation"

    Public Const CONFIGURATION_FOLDER As String = "Configuration"
    Private Const VALIDATIONLOG_PATH As String = "Configuration/Validation/ValidationLog.txt"
    Private Const EMULATOR_SETTINGSPATH As String = "Configuration/Emulator.xml"
    Private Const EMULATOR_SCHEMAPATH As String = "Configuration/Validation/Emulator.xsd"

    Private Const SKINS_SEARCHPATTERN As String = "*.Skins.xml"
    Private Const SKINS_SCHEMAPATH As String = "Configuration/Validation/Skins.xsd"

    Private Const CURSORS_SEARCHPATTERN As String = "*.Cursors.xml"
    Private Const CURSORS_SCHEMAPATH As String = "Configuration/Validation/Cursors.xsd"

    ''' <summary>
    ''' Validation event used for logging any errors with XML Config files
    ''' </summary>
    Private Shared Sub Settings_Validation(ByVal sender As Object, ByVal e As ValidationEventArgs)
        Select Case e.Severity
            Case XmlSeverityType.Error
                My.Computer.FileSystem.WriteAllText(VALIDATIONLOG_PATH, String.Format("Error: {0}", e.Message) & vbCrLf & vbCrLf, True)
            Case XmlSeverityType.Warning
                My.Computer.FileSystem.WriteAllText(VALIDATIONLOG_PATH, String.Format("Warning: {0}", e.Message) & vbCrLf & vbCrLf, True)
        End Select
    End Sub

#End Region

#Region "File Loaders"

    ''' <summary>
    ''' Loads up a new configuration object from the XML Config Files
    ''' </summary>
    ''' <returns>A new Configuration object with values set from XML Config Files</returns>
    Public Shared Function FromFiles() As Configuration
        'Delete old Validation Log
        If My.Computer.FileSystem.FileExists(VALIDATIONLOG_PATH) Then My.Computer.FileSystem.DeleteFile(VALIDATIONLOG_PATH)

        Dim Config As New Configuration()
        LoadEmulator(EMULATOR_SETTINGSPATH, EMULATOR_SCHEMAPATH, Config)
        LoadSkins(SKINS_SEARCHPATTERN, SKINS_SCHEMAPATH, Config)
        LoadCursors(CURSORS_SEARCHPATTERN, CURSORS_SCHEMAPATH, Config)

        Return Config
    End Function

    ''' <summary>
    ''' Sets up the Emulator object in a Configuration object using an XML Config file
    ''' </summary>
    ''' <param name="EmulatorPath">The Emulator.xml config file path (relative to Awesemulator)</param>
    ''' <param name="SchemaPath">The Emulator.xsd file path (relative to Awesemulator)</param>
    ''' <param name="Config">The Configuration object to add the Emulator object too</param>
    Private Shared Sub LoadEmulator(ByVal EmulatorPath As String, ByVal SchemaPath As String, ByRef Config As Configuration)
        Dim doc As New XmlDocument
        doc.Load(EmulatorPath)
        doc.Schemas.Add(String.Empty, SchemaPath)
        doc.Validate(New ValidationEventHandler(AddressOf Settings_Validation))

        'Check for validation failure (Log only exists on failure)
        If My.Computer.FileSystem.FileExists(VALIDATIONLOG_PATH) Then Throw New Exception(String.Format("{0} failed validation! Check {1} for details.", EmulatorPath, VALIDATIONLOG_PATH))

        'Map Emulator settings
        Config.Emulator = New Emulator
        Config.Emulator.ProcessName = doc.SelectSingleNode("Emulator/@ProcessName").InnerText
        Config.Emulator.Size = New Size(Integer.Parse(doc.SelectSingleNode("Emulator/@Width").InnerText), Integer.Parse(doc.SelectSingleNode("Emulator/@Height").InnerText))
        Config.Emulator.ScreenPortrait = New Rectangle(Integer.Parse(doc.SelectSingleNode("Emulator/Screen[@Orientation='Portrait']/@RelativeX").InnerText), _
                                                   Integer.Parse(doc.SelectSingleNode("Emulator/Screen[@Orientation='Portrait']/@RelativeY").InnerText), _
                                                   Integer.Parse(doc.SelectSingleNode("Emulator/Screen[@Orientation='Portrait']/@Width").InnerText), _
                                                   Integer.Parse(doc.SelectSingleNode("Emulator/Screen[@Orientation='Portrait']/@Height").InnerText))
        Config.Emulator.ScreenLandscapeLeft = New Rectangle(Integer.Parse(doc.SelectSingleNode("Emulator/Screen[@Orientation='LandscapeLeft']/@RelativeX").InnerText), _
                                                        Integer.Parse(doc.SelectSingleNode("Emulator/Screen[@Orientation='LandscapeLeft']/@RelativeY").InnerText), _
                                                        Integer.Parse(doc.SelectSingleNode("Emulator/Screen[@Orientation='LandscapeLeft']/@Width").InnerText), _
                                                        Integer.Parse(doc.SelectSingleNode("Emulator/Screen[@Orientation='LandscapeLeft']/@Height").InnerText))
        Config.Emulator.ScreenLandscapeRight = New Rectangle(Integer.Parse(doc.SelectSingleNode("Emulator/Screen[@Orientation='LandscapeRight']/@RelativeX").InnerText), _
                                                         Integer.Parse(doc.SelectSingleNode("Emulator/Screen[@Orientation='LandscapeRight']/@RelativeY").InnerText), _
                                                         Integer.Parse(doc.SelectSingleNode("Emulator/Screen[@Orientation='LandscapeRight']/@Width").InnerText), _
                                                         Integer.Parse(doc.SelectSingleNode("Emulator/Screen[@Orientation='LandscapeRight']/@Height").InnerText))

        'Map optional Helper entries
        Dim ehNodes As XmlNodeList = doc.SelectNodes("Emulator/Helper")
        If ehNodes IsNot Nothing AndAlso ehNodes.Count > 0 Then
            For Each hn As XmlNode In ehNodes
                Config.Emulator.Helpers.Add(Integer.Parse(hn.Attributes("Ratio").Value), New ScaledLayoutHelper(Double.Parse(hn.Attributes("OffsetX").Value),
                                                                                                            Double.Parse(hn.Attributes("OffsetY").Value),
                                                                                                            Double.Parse(hn.Attributes("OffsetWidth").Value),
                                                                                                            Double.Parse(hn.Attributes("OffsetHeight").Value)))
            Next
        End If
    End Sub

    ''' <summary>
    ''' Sets up the Skin objects in a Configuration object using XML Config file(s)
    ''' </summary>
    ''' <param name="SearchPattern">The file search pattern to use to find config files</param>
    ''' <param name="SchemaPath">The Skins.xsd file path (relative to Awesemulator)</param>
    ''' <param name="Config">The Configuration object to add the Skin objects too</param>
    Private Shared Sub LoadSkins(ByVal SearchPattern As String, ByVal SchemaPath As String, ByRef Config As Configuration)
        Config.Skins = New List(Of Skin)

        'There is only one skin config file by default, but this allows custom skins to be added easily
        Dim files As ReadOnlyCollection(Of String) = My.Computer.FileSystem.GetFiles(CONFIGURATION_FOLDER, SearchOption.SearchAllSubDirectories, SearchPattern)
        For Each skinConfig As String In files

            Dim doc As New XmlDocument
            doc.Load(skinConfig)
            doc.Schemas.Add(String.Empty, SchemaPath)
            doc.Validate(New ValidationEventHandler(AddressOf Settings_Validation))

            'Check for validation failure (Log only exists on failure)
            If My.Computer.FileSystem.FileExists(VALIDATIONLOG_PATH) Then Throw New Exception(String.Format("{0} failed validation! Check {1} for details.", skinConfig, VALIDATIONLOG_PATH))

            'Each skin config file can have multiple skin entries (so load each one)
            Dim skinNodes As XmlNodeList = doc.SelectNodes("Skins/Skin")
            If skinNodes IsNot Nothing AndAlso skinNodes.Count > 0 Then
                For Each skinNode As XmlNode In skinNodes
                    Dim s As New Skin() With {.Name = skinNode.Attributes("Name").Value, _
                                              .Purpose = skinNode.Attributes("Purpose").Value, _
                                              .ImagePath = skinNode.Attributes("ImageFile").Value, _
                                              .ScreenOrigin = New Point(Integer.Parse(skinNode.Attributes("RelativeX").Value), Integer.Parse(skinNode.Attributes("RelativeY").Value))}
                    Dim Use As String = skinNode.Attributes("Use").Value
                    s.UseForScreenshot = (Use = "All" OrElse Use = "Screenshot")
                    s.UseForOverlay = (Use = "All" OrElse Use = "Overlay")

                    'Map optional helper entries
                    For Each hn As XmlNode In skinNode.ChildNodes
                        'Helpers
                        s.Helpers.Add(Integer.Parse(hn.Attributes("Ratio").Value), New ScaledLayoutHelper(Double.Parse(hn.Attributes("OffsetX").Value),
                                                                                                          Double.Parse(hn.Attributes("OffsetY").Value),
                                                                                                          Double.Parse(hn.Attributes("OffsetWidth").Value),
                                                                                                          Double.Parse(hn.Attributes("OffsetHeight").Value)))
                    Next
                    Config.Skins.Add(s)
                Next
            End If
        Next
    End Sub

    ''' <summary>
    ''' Sets up the Cursor objects in a Configuration object using XML Config file(s)
    ''' </summary>
    ''' <param name="SearchPattern">The file search pattern to use to find config files</param>
    ''' <param name="SchemaPath">The Cursors.xsd file path (relative to Awesemulator)</param>
    ''' <param name="Config">The Configuration object to add the Cursor objects too</param>
    Private Shared Sub LoadCursors(ByVal SearchPattern As String, ByVal SchemaPath As String, ByRef Config As Configuration)
        Config.Cursors = New List(Of CursorInfo)

        'There is only one curosr config file by default, but this allows custom curosrs to be added easily
        Dim files As ReadOnlyCollection(Of String) = My.Computer.FileSystem.GetFiles(CONFIGURATION_FOLDER, SearchOption.SearchAllSubDirectories, SearchPattern)
        For Each cursorConfig As String In files

            Dim doc As New XmlDocument
            doc.Load(cursorConfig)
            doc.Schemas.Add(String.Empty, SchemaPath)
            doc.Validate(New ValidationEventHandler(AddressOf Settings_Validation))

            'Check for validation failure (Log only exists on failure)
            If My.Computer.FileSystem.FileExists(VALIDATIONLOG_PATH) Then Throw New Exception(String.Format("{0} failed validation! Check {1} for details.", cursorConfig, VALIDATIONLOG_PATH))

            'Each cursor config file can have multiple cursor entries (so load each one)
            Dim cursorNodes As XmlNodeList = doc.SelectNodes("Cursors/Cursor")
            If cursorNodes IsNot Nothing AndAlso cursorNodes.Count > 0 Then
                For Each cursorNode As XmlNode In cursorNodes
                    Dim c As New CursorInfo() With {.Name = cursorNode.Attributes("Name").Value, _
                                                    .Purpose = cursorNode.Attributes("Purpose").Value, _
                                                    .ImagePath = cursorNode.Attributes("ImageFile").Value, _
                                                    .ImagePathPress = cursorNode.Attributes("PressedImageFile").Value, _
                                                    .HotSpot = New Point(Integer.Parse(cursorNode.Attributes("HotspotX").Value), Integer.Parse(cursorNode.Attributes("HotspotY").Value))}
                    Config.Cursors.Add(c)
                Next
            End If
        Next
    End Sub

#End Region

#Region "Properties"
    Public Property Emulator() As Emulator
    Public Property Skins() As List(Of Skin)
    Public Property Cursors() As List(Of CursorInfo)
#End Region

End Class
